package org.jvm.rtda.heap;

import org.jvm.classfile.*;
import org.jvm.classfile.attributeinfo.SourceFileAttribute;
import org.jvm.rtda.Object;
import org.jvm.rtda.*;
import org.jvm.rtda.heap.classLoader.*;
import org.jvm.rtda.heap.classmember.*;
import org.jvm.util.JvmUtil;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 在堆中存储的Class信息
 *
 * @author 王思翔
 * @date 2023/2/4
 */
public class Klass {

    /**
     * 访问控制标识
     */
    private int accessFlags;

    /**
     * 本类的源文件名
     */
    private String sourceFile;

    /**
     * 本类的全类名
     */
    private String name;

    /**
     * 父类的全类名
     */
    private String superClassName;

    /**
     * 本类所实现的所有接口类名
     */
    private String[] interfaceNames;

    /**
     * 运行时常量池
     */
    private ConstantPool constantPool;

    /**
     * 本类所拥有的类字段信息
     */
    private Field[] fields;

    /**
     * 本类所拥有的所有方法信息
     */
    private Method[] methods;

    /**
     * 加载本类的类加载器
     */
    private AbstractKlassLoader klassLoader;

    /**
     * 父类类信息
     */
    private Klass superClass;

    /**
     * 本类实现的所有接口的类信息
     */
    private Klass[] interfaces;

    /**
     * 本类所有的实例字段数量
     */
    private int instanceSlotCount;

    /**
     * 本类所有的静态字段数量
     */
    private int staticSlotCount;

    /**
     * 本类所有的静态字段内容
     */
    private Slots staticVars;

    /**
     * 类初始化标识。表示类初始化方法是否已经开始执行
     * 类初始化时机：
     * 1、执行new指令
     * 2、执行putstatic、getstatic
     * 3、执行invokestatic
     * 3、执行类初始化时，需要先初始化其超类
     * 4、某些反射操作
     */
    private boolean initStarted = false;

    /**
     * 类对象，指java/lang/Class的实例
     * 提供给Java语言反射时获取java/lang/Class的实例使用
     */
    private Object jClass;

    public int getAccessFlags() {
        return accessFlags;
    }

    public Klass(ClassFile classFile, AbstractKlassLoader classLoader) {
        this.accessFlags = classFile.getAccessFlags();
        this.name = classFile.getClassNameByIndex(classFile.getThisClass());
        this.klassLoader = classLoader;
        this.superClassName = classFile.getClassNameByIndex(classFile.getSuperClass());
        this.interfaceNames = new String[classFile.getInterfaces().length];
        for (int i = 0; i < classFile.getInterfaces().length; i++) {
            this.interfaceNames[i] = classFile.getClassNameByIndex(classFile.getInterfaces()[i]);
        }
        this.constantPool = newConstantPool(classFile.getConstantPool());
        this.fields = newFields(classFile);
        this.methods = newMethods(classFile);
        this.sourceFile = newSourceFile(classFile);
    }

    public String getSourceFile() {
        return sourceFile;
    }

    public String newSourceFile(ClassFile classFile) {
        return Arrays.stream(classFile.getAttributes())
                .filter(attributeInfo -> attributeInfo instanceof SourceFileAttribute)
                .findAny().map(attributeInfo -> ((SourceFileAttribute) attributeInfo)
                        .fileName())
                .orElse("unkonw");
    }

    @Override
    public String toString() {
        return "Klass{" +
                "name='" + name + '\'' +
                '}';
    }

    /**
     * 返回本类的main方法
     *
     * @return
     */
    public Method getMainMethod() {
        return getStaticMethod("main", "([Ljava/lang/String;)V");
    }

    /**
     * 返回本类的初始化方法
     *
     * @return
     */
    public Method getClinitMethod() {
        return getStaticMethod("<clinit>", "()V");
    }

    /**
     * 判断本类是否是一个数组类
     * 当且仅当数组类时，类名会以[开头
     *
     * @return
     */
    public boolean isArray() {
        return this.name.startsWith("[");
    }

    /**
     * 将类名中/替换为.
     *
     * @return
     */
    public String getJavaName() {
        return this.name.replace('/', '.');
    }

    /**
     * 获取指定的静态方法
     *
     * @param name       方法名
     * @param descriptor 方法描述
     * @return
     */
    public Method getStaticMethod(String name, String descriptor) {
        return Arrays.stream(this.methods)
                .filter(method -> method.isStatic() && method.getName().equals(name) && method.getDescriptor().equals(descriptor))
                .findAny().orElse(null);
    }

    /**
     * 获取指定的实例方法
     *
     * @param name
     * @param descriptor
     * @return
     */
    public Field getInstanceField(String name, String descriptor) {
        return getField(name, descriptor, false);
    }

    public Field getField(String name, String descriptor, boolean isStatic) {
        Field f = Arrays.stream(this.fields)
                .filter(field -> !(isStatic ^ field.isStatic()) && field.getName().equals(name) && field.getDescriptor().equals(descriptor))
                .findAny().orElse(null);
        if (f != null) {
            return f;
        }
        if (this.superClass == null) {
            return null;
        }
        return this.superClass.getField(name, descriptor, isStatic);
    }

    public Field getStaticField(String name, String descriptor) {
        return getField(name, descriptor, true);
    }

    public Object getjClass() {
        return jClass;
    }

    public void setjClass(Object jClass) {
        this.jClass = jClass;
    }

    /**
     * 创建一个本类的新实例，给实例字段分配空间
     *
     * @return 返回实例的引用
     */
    public Object newObject() {
        return new Object(this, this.instanceSlotCount);
    }

    private ConstantPool newConstantPool(org.jvm.classfile.ConstantPool constantPool) {
        return new ConstantPool(this, constantPool);
    }

    private Method[] newMethods(ClassFile classFile) {
        List<Method> methodList = new ArrayList<>(classFile.getMethods().length);
        MemberInfo defineClassMock = null;
        for (int i = 0; i < classFile.getMethods().length; i++) {
            MemberInfo methodInfo = classFile.getMethods()[i];
            methodList.add(new Method(this, methodInfo));
            if (this.getName().equals("java/net/URLClassLoader")
                    && methodInfo.name().equals("defineClass")
                    && methodInfo.descriptor().equals("(Ljava/lang/String;Lsun/misc/Resource;)Ljava/lang/Class;")) {
                defineClassMock = methodInfo;
            }
        }
        if (defineClassMock != null) {
            defineClassMock.setMockName("defineClassMock");
            methodList.add(new Method(this, defineClassMock));
        }
        return methodList.toArray(new Method[0]);
    }

    private Field[] newFields(ClassFile classFile) {
        Field[] fields = new Field[classFile.getFields().length];
        for (int i = 0; i < classFile.getFields().length; i++) {
            fields[i] = new Field(this, classFile.getFields()[i]);
        }
        return fields;
    }

    public Boolean isPublic() {
        return (this.accessFlags & AccessFlags.ACC_PUBLIC.getCode()) > 0;
    }

    public Boolean isFinal() {
        return (this.accessFlags & AccessFlags.ACC_FINAL.getCode()) > 0;
    }

    public Boolean isSuper() {
        return (this.accessFlags & AccessFlags.ACC_SUPER.getCode()) > 0;
    }

    public Boolean isInterface() {
        return (this.accessFlags & AccessFlags.ACC_INTERFACE.getCode()) > 0;
    }

    public Boolean isAbstract() {
        return (this.accessFlags & AccessFlags.ACC_ABSTRACT.getCode()) > 0;
    }

    public Boolean isSynthetic() {
        return (this.accessFlags & AccessFlags.ACC_SYNTHETIC.getCode()) > 0;
    }

    public Boolean isAnnotation() {
        return (this.accessFlags & AccessFlags.ACC_ANNOTATION.getCode()) > 0;
    }

    public Boolean isEnum() {
        return (this.accessFlags & AccessFlags.ACC_ENUM.getCode()) > 0;
    }

    public AbstractKlassLoader getKlassLoader() {
        return klassLoader;
    }

    public void setKlassLoader(AbstractKlassLoader klassLoader) {
        this.klassLoader = klassLoader;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSuperClassName() {
        return superClassName;
    }

    public void setSuperClassName(String superClassName) {
        this.superClassName = superClassName;
    }

    public String[] getInterfaceNames() {
        return interfaceNames;
    }

    public void setInterfaceNames(String[] interfaceNames) {
        this.interfaceNames = interfaceNames;
    }

    public ConstantPool getConstantPool() {
        return constantPool;
    }

    public void setConstantPool(ConstantPool constantPool) {
        this.constantPool = constantPool;
    }

    public Field[] getFields() {
        return fields;
    }

    public boolean isInitStarted() {
        return initStarted;
    }

    /**
     * 开始初始化过程
     */
    public void startedInit() {
        this.initStarted = true;
    }

    public void setFields(Field[] fields) {
        this.fields = fields;
    }

    public Method[] getMethods() {
        return methods;
    }

    public Method[] getMethods(boolean publicOnly) {
        return Arrays.stream(this.methods)
                .filter(m -> !publicOnly || m.isPublic())
                .collect(Collectors.toList())
                .toArray(new Method[0]);
    }


    public void setMethods(Method[] methods) {
        this.methods = methods;
    }

    public Klass getSuperClass() {
        return superClass;
    }

    public void setSuperClass(Klass superClass) {
        this.superClass = superClass;
    }

    public Klass[] getInterfaces() {
        return interfaces;
    }

    public void setInterfaces(Klass[] interfaces) {
        this.interfaces = interfaces;
    }

    public int getInstanceSlotCount() {
        return instanceSlotCount;
    }

    public void setInstanceSlotCount(int instanceSlotCount) {
        this.instanceSlotCount = instanceSlotCount;
    }

    public int getStaticSlotCount() {
        return staticSlotCount;
    }

    public void setStaticSlotCount(int staticSlotCount) {
        this.staticSlotCount = staticSlotCount;
    }

    public Slots getStaticVars() {
        return staticVars;
    }

    public void setStaticVars(Slots staticVars) {
        this.staticVars = staticVars;
    }

    /**
     * 检查本类是否可以被其他类访问
     *
     * @param thatClass 其他类
     * @return
     */
    public boolean isAccessibleTo(Klass thatClass) {
        //public允许任何访问 或 两个类位于同一个包中
        return isPublic() || getPackageName().equals(thatClass.getPackageName());
    }

    /**
     * 本类所在包名
     * 如果类定义在默认包中，则其包名为空字符串
     *
     * @return
     */
    public String getPackageName() {
        int i = this.name.lastIndexOf('/');
        if (i >= 0) {
            return this.name.substring(0, i);
        } else {
            return "";
        }
    }

    /**
     * 判断本类是否继承了thatClass(同一类不认为继承自身)
     * 注意这里不检查接口。某类实现接口并不是类继承了接口，某类亦不是接口的子类
     *
     * @param thatClass
     * @return
     */
    public boolean isSubClassOf(Klass thatClass) {
        //Object没有父类，同时Object不可能是任何类的子类
        if (this.superClass == null) {
            return false;
        }
        if (this.superClass == thatClass) {
            return true;
        }
        return this.superClass.isSubClassOf(thatClass);
    }

    /**
     * @param thatClass
     * @return
     */
    public boolean isSuperClassOf(Klass thatClass) {
        return thatClass.isSubClassOf(this);
    }

    public Method getInstanceMethod(String name, String descriptor) {
        return Arrays.stream(this.methods)
                .filter(method -> !method.isStatic() && method.getName().equals(name) && method.getDescriptor().equals(descriptor))
                .findAny().orElse(null);
    }

    /**
     * 判断本类是否实现了接口thatInterface
     *
     * @param thatInterface
     * @return
     */
    public boolean isImplements(Klass thatInterface) {
        if (this == thatInterface) {
            return true;
        }
        for (Klass i : this.getInterfaces()) {
            if (i.isImplements(thatInterface)) {
                return true;
            }
        }
        //Object没有父类，同时Object不会实现任何接口
        if (this.superClass == null) {
            return false;
        }
        return this.superClass.isImplements(thatInterface);
    }

    public Klass() {
    }

    /**
     * 创建数组类
     *
     * @param className
     * @param classLoader
     * @return
     */
    public static Klass newArrayKlass(String className, BootKlassLoader classLoader) {
        Klass arrayClass = new Klass();
        //数组类应当是public的
        arrayClass.accessFlags = AccessFlags.ACC_PUBLIC.getCode();
        arrayClass.name = className;
        //数组类无需初始化
        arrayClass.initStarted = true;
        arrayClass.klassLoader = classLoader;
        //数组类应当继承自所有类的父类
        arrayClass.superClass = classLoader.loadKlass("java/lang/Object");
        //数组类默认实现两个接口
        arrayClass.interfaces = new Klass[]{classLoader.loadKlass("java/lang/Cloneable")
                , classLoader.loadKlass("java/io/Serializable")};
        return arrayClass;
    }

    /**
     * 创建基本类型类
     *
     * @param className
     * @param classLoader
     * @return
     */
    public static Klass newPrimitiveClass(String className, BootKlassLoader classLoader) {
        Klass primitiveClass = new Klass();
        //基本类型的类没有父类，也不实现任何接口
        primitiveClass.accessFlags = AccessFlags.ACC_PUBLIC.getCode();
        primitiveClass.name = className;
        primitiveClass.initStarted = true;
        primitiveClass.klassLoader = classLoader;
        return primitiveClass;
    }


    /**
     * 根据本数组类的类型，创建出一个相应的数组实例
     *
     * @param count 数组长度
     * @return
     */
    public Object newArray(int count) {
        if (!isArray()) {
            throw new RuntimeException("not array");
        }
        switch (this.getName()) {
            case "[Z":
                return new Object(this, new byte[count]);
            case "[B":
                return new Object(this, new byte[count]);
            case "[C":
                return new Object(this, new char[count]);
            case "[S":
                return new Object(this, new short[count]);
            case "[I":
                return new Object(this, new int[count]);
            case "[J":
                return new Object(this, new long[count]);
            case "[F":
                return new Object(this, new float[count]);
            case "[D":
                return new Object(this, new double[count]);
            //引用类型数组
            default:
                return new Object(this, new Object[count]);
        }
    }

    /**
     * 根据字段名，字段类型，给静态的字段赋值
     *
     * @param filedName
     * @param filedType
     * @param ref
     */
    public void setStaticRefVar(String filedName, String filedType, Object ref) {
        Field field = this.getStaticField(filedName, filedType);
        int slotId = field.getSlotId();
        this.staticVars.setRef(slotId, ref);
    }

    public Object getStaticRefVar(String filedName, String filedType) {
        Field field = this.getStaticField(filedName, filedType);
        int slotId = field.getSlotId();
        return this.staticVars.getRef(slotId);
    }

    /**
     * 判断本类是否是基本类型
     *
     * @return
     */
    public Boolean isPrimitive() {
        return JvmUtil.primitiveTypes.containsKey(this.name);
    }

    /**
     * 获取当前数组类数组成员类
     *
     * @return
     */
    public Klass getComponentClass() {
        if (!isArray()) {
            throw new RuntimeException("not array");
        }
        return this.klassLoader.loadKlass(getComponentClassName(), null);
    }

    public String getComponentClassName() {
        String componentClassName;
        //去掉前缀的一个[
        String subName = this.name.substring(1);
        //如果前缀依旧是[，说明依旧是一个数组类
        if (subName.startsWith("[")) {
            componentClassName = subName;
        }
        //如果前缀是L，则去掉L和末尾的;就是类名
        else if (subName.startsWith("L")) {
            componentClassName = subName.substring(1, subName.length() - 1);
        }
        //剩下的就是基本类型，根据描述符寻找基本类型名
        else {
            componentClassName = JvmUtil.primitiveTypes.entrySet().stream()
                    .filter(entry -> entry.getValue().equals(subName))
                    .map(Map.Entry::getKey).findAny().orElse("");
        }
        return componentClassName;
    }


    /**
     * 判断本类是否可以从thatClass转化而来
     * 1、本类为类，thatClass继承了本类
     * 2、本类为接口，thatClass实现了接口
     * <p>
     * 特别的，如果下面两个条件之一成立，类型为[]SC的数组可以转换为[]TC的数组
     * 1、TC与SC是同一种基本类型
     * 2、TC与SC都是引用类型，且SC可以转换为TC
     *
     * @param thatClass
     * @return
     */
    public boolean isAssignableFrom(Klass thatClass) {
        if (this == thatClass) {
            return true;
        }
        if (!isArray()) {
            if (isInterface()) {
                return thatClass.isImplements(this);
            } else {
                return thatClass.isSubClassOf(this);
            }
        } else {
            String thisComponentClassName = this.getComponentClassName();
            String thatComponentClassName = thatClass.getComponentClassName();
            boolean thisComponentIsPrimitive = JvmUtil.primitiveTypes.containsKey(thisComponentClassName);
            boolean thatComponentIsPrimitive = JvmUtil.primitiveTypes.containsKey(thatComponentClassName);
            if (thisComponentIsPrimitive && thatComponentIsPrimitive) {
                return thisComponentClassName.equals(thatComponentClassName);
            } else if (!thisComponentIsPrimitive && !thatComponentIsPrimitive) {
                Klass thisComponentClass = klassLoader.loadKlass(thisComponentClassName, null);
                Klass thatComponentClass = klassLoader.loadKlass(thatComponentClassName, null);
                return thisComponentClass.isAssignableFrom(thatComponentClass);
            }
            return false;
        }
    }

    public Method[] getConstructors(boolean publicOnly) {
        return Arrays.stream(this.methods)
                .filter(m -> m.isConstructor() && (!publicOnly || m.isPublic()))
                .collect(Collectors.toList())
                .toArray(new Method[0]);
    }

    /**
     * 返回一个以本类为元素的数组类
     *
     * @return
     */
    public Klass arrayClass() {
        return KlassLoaderRegister.getBootKlassLoader().loadKlass("[" + JvmUtil.toDescriptor(getName()));
    }

    /**
     * 根据参数列表获取相应的构造器
     *
     * @param descriptor
     */
    public Method getConstructor(String descriptor) {
        return getInstanceMethod("<init>", descriptor);
    }

    public Field[] getFields(boolean publicOnly) {
        return Arrays.stream(this.fields)
                .filter(f -> !publicOnly || f.isPublic())
                .collect(Collectors.toList())
                .toArray(new Field[0]);
    }
}
